

import * as fs from "fs";
import * as path from "path";
import PSD, { Group, Layer, PSDFile, TreeRootNode } from "psd";
import packageJSON from "../../../../import";
import { ComMgr } from "./com";
let psd: PSD = require("psd");
// let psd;

/**
 *  图层/文件 命名：
 *  [name]component1(property1=value;property2=value){function1=value1,value2;function2=value1}
 *  @component2(property1=value,property2=value){function}
 *  [节点名字]组件1名字(属性1=值;属性2=值){调用函数1=参数1,参数2;调用函数2=参数1}
 *  @组件2名字(属性1=值;属性2=值){调用函数1=参数1,参数2;调用函数2=参数1}...
 *  其中:[name]必填，其他选填， @分割组件
 *
 *  *实例1：[test]sprite(spriteFrame=test)@BoxCollider(Tag=1){init} =>
 *  创建一个test节点，添加上sprite组件，并设置图片为test的图片，然后添加盒子碰撞器，并设置Tag = 1， 然后执行init函数
 * 
 *  *实例2：[test2]sprite(test2)@BoxCollider(1){init} => 同上，可以自定义编写特定构造传参，并且不用写属性
 *  自定义构造参数类似：
 *  sprite(name:string){
 *      this.name = name;
 *  }
 *  也就是说甚至可以省略掉参数
 *  sprite(){
 *      spriteFrame = this.image; //绑定该节点的图片自身，即test2图层的图片
 *  }
 *  使用：[test2]sprit
 *
 *  *实例3：[test3]label("测试文本";30;#978689) =>
 *  label(parm1, parm2, parm3){
 *      this.string = parm1;
 *      this.fontSize = parm2;
 *      this.color = new cc.Color(parm3);
 *  }
 *  表示创建test3节点，设置节点的文本，字体大小，颜色，不过，该传参具有局限，参数必须与自定义构造参数设置的接口一致,不然传参错误
 * 
 *  *实例4：[test4]label("测试文本";fontSize=30;color=#978689)
 * 
 *  *实例5: 首先自定义简写查找表
 *  {
 *    SP = sprite,
 *    LB = label,
 *    BOX = BoxCollider
 *  }
 *  然后自定义构造参数
 *  {
 *     sprite(name:string){
 *         spriteFrame = name;
 *     }
 *  }
 *  使用： [test5]SP(test)@LB("测试文本") //自定义无参构造后 [test5]SP@LB
 *  虽然简写表可以小写，推荐简写表字母大写（LB),ps方便辨识
 */


/**
 *   Map<comName, { propertyList: kvType; funcList: kvType; }> 
 *   节点： a
 *          com1  (){}
 *          com2  (){}
 *          com3  (){}
 */
/**组件数据结构 */
type kvType = Map<string, string[]>;
type dataType = { propertyList: kvType; funcList: kvType; };
export type comType = Map<string, dataType>;

/**节点树 */
interface NodeTree {
    /**节点名字 */
    nodeName: string;
    /**节点组件参数 */
    comData: comType;
    /**组件原始字符串 */
    comStr: string;
    /**原始图层字符串 */
    layarStr: string;
    /**子节点 */
    child: NodeTree[];
    /**父节点 */
    parent: NodeTree;
    /**节点所对应的Psd数据 */
    psdData: Layer | Group;
}

/**缓存数据 */
type I_CacheData = {
    psdName: string;
    /**预制体的缓存uuid */
    prefabUuid: string;
    /**图片duuid列表 */
    imgUuidList: string[];
};

/**
 *  第一次生成时，保存预制体的uuid，和图片映射的uuid，并缓存
 *  第二次修改时，直接查找图片的uuid并更新
//  *   读取Psd文件夹
//  *   解析psd图层名字
 *   保存psd图片png到文件夹
 *   根据参数创建节点
 *   添加到场景
 *    保存的格式
 *     db://assets/${this.saveFileName}/${psdName}/${name}
 *     db://assets/${this.saveFileName}/${psdName}/img/${name}
 */
export class ParsePSD {
    /**存放psd的文件夹path */
    psdFloderPath: string = ``;
    /**存放关系映射表的path */
    relationShipPath: string = ``;
    /**uuid缓存json文件的路径 */
    cacheFilePath: string = ``;

    /**存放在项目的里的路径（prefab和img） */
    saveProjectPath: string = ``;

    /**组件名字映射表 */
    comRelationShipMap: Map<string, string> = new Map();
    /**存储加载出来的psd文件 */
    psdDataMap: Map<string, PSDFile> = new Map();
    /**存放解析出来的节点数 */
    nodeThreeMap: Map<string, NodeTree> = new Map();
    /**缓存的uuid数据 */
    uuidCacheMap: Map<string, I_CacheData> = new Map();

    init() {
        let packagePath = Editor.Package.getPath(packageJSON.name);
        this.saveProjectPath = `assets/test`;
        this.relationShipPath = packagePath + '\\src\\panels\\default\\data\\comRelationShip.json';
        this.cacheFilePath = packagePath + '\\src\\panels\\default\\data\\uuidCache.json';
        let psdFloderPath = packagePath + '\\psd';
        let prefabName = "Sample";
        let psdName = "Sample";
        this.loadRelationJson(this.relationShipPath, this.cacheFilePath);
        this.loadPsdAndParse(psdFloderPath);
        this.transToPrefab(psdName, prefabName);
    }

    //#region 解析psd

    /**
     * 加载关系路径表和缓存uuid
     * @param filePath 关系映射表的文件路径
     * @param cachePath uuid缓存文件路径
     */
    loadRelationJson(filePath: string, cachePath: string) {
        let jsonData: [];
        try {
            //加载关系表
            let fileData = `{}`;
            if (fs.existsSync(filePath)) {
                fileData = fs.readFileSync(filePath, 'utf-8');
                if (fileData == null)
                    throw Error(`读取关系表失败!`);
            }
            jsonData = JSON.parse(fileData);
            this.comRelationShipMap = new Map(Object.entries(jsonData));


            //加载uuid缓存文件
            fileData = `{}`;
            if (fs.existsSync(cachePath)) {
                fileData = fs.readFileSync(cachePath, 'utf-8');
                if (fileData == null)
                    return;
            }
            jsonData = JSON.parse(fileData);
            this.uuidCacheMap = new Map(Object.entries(jsonData));
        } catch (error) {
            console.error(`加载关系映射表和缓存uuid失败！${error},路径:${filePath}`);
        }
    }

    /**
     * 存储关系表和缓存到指定path
     */
    writeRelationJson() {
        try {
            //存储关系表
            let object: { [key: string]: any } = {};
            this.comRelationShipMap.forEach((v, k) => object[k] = v);
            let writeData = JSON.stringify(object);
            fs.writeFileSync(this.relationShipPath, writeData);
            //存储uuid表
            object = {};
            this.uuidCacheMap.forEach((v, k) => object[k] = v);
            fs.writeFileSync(this.cacheFilePath, JSON.stringify(object));
        } catch (error) {
            console.error(`存储关系映射表和缓存uuid失败！${error},路径:${this.relationShipPath}`);
        }
    }

    /**
     * 加载和解析psd文件数据
     * @param psdPath 存储psd文件的文件夹
     */
    loadPsdAndParse(psdPath: string) {
        try {
            this.psdFloderPath = psdPath;
            let nameList = this.getPsdFileListName(psdPath);
            let _parse = (Nt: Group | Layer, parentNode: NodeTree, psdName: string) => {
                let curNode = {} as NodeTree;
                curNode.child = [];
                curNode.parent = parentNode;
                parentNode.child.push(curNode);

                let { nodeName, comStr } = this.paresLayerStr(psdName, Nt.name);
                curNode.comStr = comStr;
                curNode.layarStr = Nt.name;
                curNode.nodeName = nodeName;
                curNode.psdData = Nt;
                curNode.comData = this.paresComStr(psdName, comStr, nodeName);
                for (let child of Nt._children) {
                    _parse(child, curNode, psdName);
                }
            };
            //解析psd节点树
            for (let [name, path] of nameList.entries()) {
                let psdFile = psd.fromFile(path);
                psdFile.parse();

                let psdThree = psdFile.tree();
                let rootNode = {} as NodeTree;
                rootNode.child = [];
                for (let childNt of psdThree._children) {
                    _parse(childNt, rootNode, name);
                }
                this.psdDataMap.set(name, psdFile);
                this.nodeThreeMap.set(name, rootNode);
                // console.log(`name:${name},parseData:`, rootNode);
            }
        } catch (error) {
            console.error(`加载psd和解析psd出错:${psdPath},error:${error}`);
        }
    }

    /**
     * 获取psd文件path列表
     * @param folderPath psd文件夹地址     
     * @returns Map<name, path>，文件夹下所有psd名字和路径map
     */
    getPsdFileListName(folderPath: string) {
        //校验地址
        if (!RegExp(/^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+)?$/).test(folderPath))
            throw Error(`psd名字不正确`);

        let files = fs.readdirSync(folderPath);
        let fileData: Map<string, string> = new Map();
        for (let fileFullName of files) {
            let filePath = path.join(folderPath, fileFullName);
            let parStr = fileFullName.split('.');
            let stat = fs.statSync(filePath);
            if (stat.isFile() && parStr[1] == "psd") {
                fileData.set(parStr[0], filePath);
            }
        }
        return fileData;
    }

    /**
     * 根据文件名获取转换后存储的项目path
     * @param psdName psd文件夹名字
     * @param transName 转换的文件夹名
     * @returns 转换后的相对路径
     */
    getSaveTransPath(psdName: string, transName: string) { return `db://${this.saveProjectPath}/${psdName}/${transName}`; }

    /**
     * 根据名字获取预制体在项目里的相对路径
     * @param psdName psd名字
     * @param prefabName 预制体名字
     * @returns 转换保存在项目的相对路径
     */
    getSavePrefabPath(psdName: string, prefabName: string) { return this.getSaveTransPath(psdName, prefabName) + `.prefab`; }

    /**
     * 根据参数获取保存图片的相对路径
     * @param psdName psd名字
     * @param imgName 图片的名字
     * @returns 转换的保存路径
     */
    getSaveImagePath(psdName: string, imgName: string) { return this.getSaveTransPath(psdName, imgName) + `.png`; }

    //#endregion

    /**
     * 转换指定是psd到指定预制体，如果已存在预制体就只是替换图片，且如果修改了psd层级，也并不会更新预制体，只会更新图片
     * @param psdName 要转换的psd的名字
     * @param prefabName 要保存的预制体名字
     */
    async transToPrefab(psdName: string, prefabName: string) {
        try {
            prefabName = psdName;
            let nodeThree = this.nodeThreeMap.get(prefabName);
            if (nodeThree) {
                /**
                 *  首先根据记录的uuid查找Prefab是否创建
                 *   是 ：然后获取记录的图片资源的文件夹的uuid路径，然后批量更新替换图片
                 *       获取prefab 然后覆盖
                 *   否： 根据指定位置创建图片资源文件夹，并记录uuid, 然后创建预制体，绑定脚本，
                 */
                Editor.Message.request<'', ''>('', '')
                let path = Editor.remote.assetdb.uuidToUrl(this.uuidCacheMap.get(psdName)?.prefabUuid);
                let has = path != null && path != ``;
                if (has) {
                    //获取图片资源文件夹，替换掉
                    this.replaceImage(nodeThree);
                    //创建prefab，替换
                } else {
                    //创建图片资源文件夹
                    let spriteFloder = await assetdbCreateAsync("db://assets/Texture/");
                    console.log(spriteFloder);
                    //存储uuid;
                    //创建预制体，然后保存到项目里
                    let createNode = this.createPrefab(nodeThree);
                    let imgUuidList: string[] = [];
                    //缓存uuid
                    this.uuidCacheMap.set(psdName, { psdName, prefabUuid: createNode.uuid, imgUuidList })
                }
            } else
                throw new Error(`未查找到要创建/替换图片的预制体:${prefabName}，没有psd：${psdName}节点解析数据`);
        } catch (error) {
            console.error(`psd:${psdName}转换成节点:${prefabName}失败，error:${error}`);
        }
    }

    /**
     * 根据解析的数据生成相应的cc节点数据
     * @param nodeData 要生成的节点数据
     * @returns 节点数据
     */
    createPrefab(nodeData: NodeTree) {
        try {
            let rootNode = new cc.Node(nodeData.nodeName ?? `defaultNode`);
            for (let childData of nodeData.child) {
                let childNode = this.createPrefab(childData);
                rootNode.addChild(childNode);
                //添加组件
                let comsData = childData.comData;
                if (comsData == null)
                    throw Error(`没有节点${childData.nodeName}的组件数据`);
                for (let [name, comData] of comsData.entries()) {
                    let comName = this.comRelationShipMap.get(name);
                    if (comName == null || comName == '') {
                        console.warn(`没有查找到${name}所对应的关系组件名字！`);
                        continue;
                    }
                    let comParseClass = ComMgr.ins.getComFun(comName, nodeData);
                }
            }
            return rootNode;
        } catch (error) {
            console.error(`生成节点失败：${nodeData.nodeName},error:${error}`);
        }
    }

    /**
     * 根据节点数据替换项目中对应名字的图片
     * @param nodeData 节点数据
     */
    replaceImage(nodeData: NodeTree) {
        let each = (list: NodeTree) => {
            for (let node of Object.values(list.child)) {
                //TODO
                node.nodeName
            }
        }
        each(nodeData);
    }

    newCom(comStr: string, nodeData: NodeTree) {

    }

    /**
     * 存储图片
     * @param imgLayer 存储图片的psd文档数据
     * @param name 要存储的图片名字
     * @param path 存储地址
     */
    savePng(imgLayer: Group | Layer, name: string, path = this.psdFloderPath) {
        let savePath = path + `\\${name}.png`;
        imgLayer.layer.image.saveAsPng(savePath);
    }

    /**
     * 检查字符是否为空
     * @param psdName psd名字
     * @param str 要检查的字符
     */
    checkStr(psdName: string, str: string) {
        if (psdName == `` || psdName == null)
            throw Error(`psdName为空！`);
        if (str == null)
            throw Error(`psd:${psdName}，str错误！`);
    }

    /**
     * 根据节点信息，节点名字，组件名字查找组件信息
     * @param node 节点信息
     * @param nodeName 要查找的节点名字
     * @param comName 要查找的节点上的组件名字
     * @returns 查找到的组件信息
     */
    getPropertyData(node: NodeTree, nodeName: string, comName: string): dataType | undefined {
        let comData = node.comData.get(comName);
        if (node.nodeName == nodeName && comData)
            return comData;
        //遍历子节点
        for (let childNode of node.child) {
            comData = this.getPropertyData(childNode, nodeName, comName);
            if (comData)
                return comData;
        }
        return undefined;
    }

    /**
     *  分割图层/分层组的名字，返回名字，和组件参数字符
     * @param psdName psd名字
     * @param layerStr psd图层/分组层原始字符数据
     * @returns {nodeName, comstr} 节点名字，组件参数字符
     */
    paresLayerStr(psdName: string, layerStr: string) {
        this.checkStr(psdName, layerStr);

        let parData = { nodeName: ``, comStr: `` };
        if (layerStr == ``) {
            console.warn(`psd:${psdName}，有图层layerStr为空！`);
            parData.nodeName = `${psdName}-${Editor.Utils.UuidUtils.decompressUuid(Editor.Utils.UuidUtils.uuid())}`;
            return parData;
        }
        //判断开头是否包含[]
        if (!RegExp(/^\[.*\]/).test(layerStr)) {
            parData.nodeName = layerStr;
            return parData;
        }
        //判断是否有嵌套[],如果有嵌套的取最后一个]
        if (layerStr.split(`]`).length > 2) {
            let lastIndex = layerStr.lastIndexOf(`]`);
            parData.nodeName = layerStr.slice(1, lastIndex);
            parData.comStr = layerStr.slice(lastIndex + 1);
            return parData;
        }
        //正常分割[]和后面的组件参数
        let parStr = layerStr.split(/\[|\]/);
        parData.nodeName = parStr[1];
        parData.comStr = parStr[2];
        return parData;
    }

    /**
     * 根据字符数据解析成对应的数据结构
     *  component1(property1=value;property2=value){function1=value1,value2;function2=value1}...
     * @param psdName psd名字
     * @param parStr 组件原始字符数据
     * @param layerName 图层/分组名字
     * @returns Map<comName, comData>，解析后的数据
     */
    paresComStr(psdName: string, parStr: string, layerName?: string) {
        this.checkStr(psdName, parStr);

        let comDataList: comType = new Map();
        if (parStr == ``) {
            return comDataList;
        }

        //分割@
        let comStrList = parStr.split(`@`);
        comStrList = comStrList.filter((x) => x !== '');
        if (comStrList.length >= 1) {
            let propertyList: kvType, funcList: kvType, comName = ``;
            comStrList.forEach(comStr => {
                if (comStr == ``) return;

                //过滤掉函数和属性
                let matchProperty = comStr.match(/\(([^]+?)\)/);
                let matchFun = comStr.match(/\{([^]+?)\}/);
                if (!matchProperty && !matchFun && /\{|\}|\(|\)|\@/.test(comStr))
                    comStr = comStr.replace(/(\{|\}|\(|\)|\@)+/, ``);

                //获取组件名
                comName = comStr.split(`(`)[0];
                if (comName == `` || comStr == ``)
                    return;//Editor.log(`psd:${psdName}层级:${layerName}没有组件名，组件参数：${comStr}`);

                //获取分割后的属性参数键对值
                propertyList = this._getComData(matchProperty);
                funcList = this._getComData(matchFun, true);

                comDataList.set(comName, { propertyList, funcList });
            });
        }
        return comDataList;
    }

    /**
     * 根据正则匹配的结果返回解析后的数据
     * @param matchData 正则匹配字符串的数据 => property1=value;property2=value  or  function1=value1,value2;function2=value1
     * @param isFun 是否是函数参数{}
     * @returns Map<Name,parme>,解析的数据
     */
    _getComData(matchData: RegExpMatchArray | null, isFun = false) {
        let propertyMap: kvType = new Map();
        if (matchData == null)
            return propertyMap;

        //分割属性字符
        let keyValues = matchData[1].split(`;`);
        keyValues.forEach((kvStr, index) => {
            let propertyName = ``, kvList: string[] = [];
            if (kvStr != ``) {
                //分割处理参数字符
                let kv = kvStr.split(`=`);
                if (kv.length != 1) {
                    //传的是【属性-值】列表
                    propertyName = kv[0];
                    kvList = kv[1].split(`,`);
                } else {
                    //传的是【单参数】
                    if (isFun) {
                        //处理函数的参数，传的是函数名
                        propertyName = kv[0];
                    } else {
                        //处理组件的参数，传的是值，按序号赋值参数
                        propertyName = index.toString();
                        kvList = kv[0].split(`,`);
                    }
                }
                propertyMap.set(propertyName, kvList);
            }
        });
        return propertyMap;
    }
}

/**
 * 导入资源
 * @param paths 资源
 * @param url 资源地址
 * @returns
 */
// function assetdbImportAsync(paths: any, url: any): Promise<{}> {
//     return new Promise(function (resolve, reject) {
//         Editor.assetdb.import(paths, url, (err, results: {}[]) => {
//             if (err) {
//                 reject(err);
//             }
//             resolve(results);
//         });
//     });
// }

// function ccLoaderLoadAsync(uuid: string): Promise<{}> {
//     return new Promise(function (resolve, reject) {
//         cc.loader.load({ type: "uuid", uuid: uuid }, null, (err, asset) => {
//             if (err) {
//                 reject(err);
//             }
//             // cc.Texture2D
//             // asset.getTexture().getPixelFormat();asset.getTexture()._texture
//             resolve(asset);
//         });
//     });
// }

// /**
//  * 根据资源地址创建资源映射
//  * @param url 资源地址
//  * @returns result
//  */
// function assetdbCreateAsync(url) {
//     return new Promise(function (resolve, reject) {
//         Editor.assetdb.create(url, null, (err, results: {
//             hidden: boolean;
//             parentUuid: string;//"mount-assets";
//             path: string; //"F:\cocosgame\DevNew\psdToPrefab\assets\Texture";
//             readonly: boolean;
//             type: string;//"folder";
//             url: string;// "db://assets/Texture";
//             uuid: string;
//         }[]) => {
//             if (err) {
//                 reject(err);
//             }
//             resolve(results);
//         });
//     });
// }
// function assetdbCreateOrSaveAsync(url) {
//     return new Promise(function (resolve, reject) {
//         Editor.assetdb.createOrSave(url, null, (err, results: {}[]) => {
//             if (err) {
//                 reject(err);
//             }
//             resolve(results);
//         });
//     });
// }
// function assetdbQueryPathByUrlAsync(url) {
//     return new Promise(function (resolve, reject) {
//         //@ts-ignore
//         Editor.assetdb.queryPathByUrl(url, (err, results: {}[]) => {
//             if (err) {
//                 reject(err);
//             }
//             resolve(results);
//         });
//     });
// }

// /**
//  * 根据url查找资源
//  * @param url url
//  * @returns asset
//  */
// function assetdbQueryUuidByUrlAsync(url: string) {
//     return new Promise(function (resolve, reject) {
//         //@ts-ignore
//         Editor.assetdb.queryUuidByUrl(url, (err, results: {}[]) => {
//             if (err) {
//                 reject(err);
//             }
//             resolve(results);
//         });
//     });
// }
// function assetdbQueryAssetsAsync(pattern, type): Promise<{}[]> {
//     return new Promise(function (resolve, reject) {
//         Editor.assetdb.queryAssets(pattern, type, (err, results: {}[]) => {
//             if (err) {
//                 reject(err);
//             }
//             resolve(results);
//         });
//     });
// }
// function assetdbQueryMetaInfoByUuidAsync(uuid: string): Promise<{}> {
//     return new Promise(function (resolve, reject) {
//         //@ts-ignore
//         Editor.assetdb.queryMetaInfoByUuid(uuid, (err, metaInfo) => {
//             if (err) {
//                 reject(err);
//             }
//             resolve(metaInfo);
//         });
//     });
// }
// function assetdbsaveMetaAsync(uuid: string, jsonString: string): Promise<{}> {
//     return new Promise(function (resolve, reject) {
//         Editor.assetdb.saveMeta(uuid, jsonString, (err, meta) => {
//             if (err) {
//                 reject(err);
//             }
//             resolve(meta);
//         });
//     });
// }



//克隆
// const isType = (obj, type) => {
//     if (typeof obj !== 'object') return false;
//     // 判断数据类型的经典方法：
//     const typeString = Object.prototype.toString.call(obj);
//     let flag;
//     switch (type) {
//         case 'Array':
//             flag = typeString === '[object Array]';
//             break;
//         case 'Date':
//             flag = typeString === '[object Date]';
//             break;
//         case 'RegExp':
//             flag = typeString === '[object RegExp]';
//             break;
//         default:
//             flag = false;
//     }
//     return flag;
// };

// const getRegExp = re => {
//     var flags = '';
//     if (re.global) flags += 'g';
//     if (re.ignoreCase) flags += 'i';
//     if (re.multiline) flags += 'm';
//     return flags;
// };

// /**
// * deep clone
// * @param  {[type]} parent object 需要进行克隆的对象
// * @return {[type]}        深克隆后的对象
// */
// const clone = parent => {
//     // 维护两个储存循环引用的数组
//     const parents = [];
//     const children = [];

//     const _clone = parent => {
//         if (parent === null) return null;
//         if (typeof parent !== 'object') return parent;

//         let child, proto;

//         if (isType(parent, 'Array')) {
//             // 对数组做特殊处理
//             child = [];
//         } else if (isType(parent, 'RegExp')) {
//             // 对正则对象做特殊处理
//             child = new RegExp(parent.source, getRegExp(parent));
//             if (parent.lastIndex) child.lastIndex = parent.lastIndex;
//         } else if (isType(parent, 'Date')) {
//             // 对Date对象做特殊处理
//             child = new Date(parent.getTime());
//         } else {
//             // 处理对象原型
//             proto = Object.getPrototypeOf(parent);
//             // 利用Object.create切断原型链
//             child = Object.create(proto);
//         }

//         // 处理循环引用
//         const index = parents.indexOf(parent);

//         if (index != -1) {
//             // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
//             return children[index];
//         }
//         parents.push(parent);
//         children.push(child);

//         // 递归
//         for (let i in parent) {
//             child[i] = _clone(parent[i]);
//         }

//         return child;
//     };
//     return _clone(parent);
// };




